use std::ops::{Deref, DerefMut};

pub struct DirtyMark<T> {
    v: T,
    dirty: bool,
}

impl<T> DirtyMark<T> {
    pub fn new(v: T) -> Self {
        Self {
            v,
            dirty: true,
        }
    }

    #[inline]
    pub fn is_dirty(&self) -> bool {
        self.dirty
    }

    #[inline]
    pub fn mark_dirty(&mut self) {
        self.dirty = true;
    }

    #[inline]
    pub fn unmark_dirty(&mut self) {
        self.dirty = false;
    }

    #[inline]
    pub fn inner(&self) -> &T {
        &self.v
    }

    #[inline]
    pub fn inner_mut(&mut self) -> &mut T {
        self.mark_dirty();
        &mut self.v
    }
}

impl<T> DerefMut for DirtyMark<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.inner_mut()
    }
}

impl<T> Deref for DirtyMark<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        self.inner()
    }
}

#[cfg(test)]
mod test {
    use crate::DirtyMark;

    #[test]
    fn ops_dirty() {
        let mut a = DirtyMark::new(1);
        *a += 1;
        assert_eq!(a.is_dirty(), true);
        a.unmark_dirty();
        assert_eq!(a.is_dirty(), false);
    }

    #[test]
    fn method_dirty() {
        let mut a = DirtyMark::new(vec![1, 2, 3]);
        a.push(4);
        assert_eq!(a.is_dirty(), true);
        a.unmark_dirty();
        assert_eq!(a.is_dirty(), false);
    }

    #[test]
    fn borrow_dirty() {
        struct Host {
            data: DirtyMark<i32>,
        }

        impl Host {
            fn new(data: i32) -> Self {
                Self { data: DirtyMark::new(data) }
            }
            fn data_mut(&mut self) -> &mut i32 {
                &mut self.data
            }
        }

        let mut h = Host::new(10);
        let _ = h.data_mut();

        assert_eq!(h.data.is_dirty(), true)
    }
}